package com.skyable.common.config;

import com.skyable.common.annotation.SaFeignClient;
import com.skyable.common.annotation.SaScan;
import com.skyable.common.utils.ClassUtil;
import com.skyable.common.utils.PropertiesUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.core.env.Environment;
import org.springframework.web.context.support.WebApplicationContextUtils;

import java.lang.annotation.Annotation;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * 用于Spring动态注入自定义接口
 *
 * @author gaoyuan
 */
@Slf4j
@Data
@Configuration
public class ServiceBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor,EnvironmentAware {

    private Environment environment;

	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

		Class springBootApplicationRunClass = this.init();

		//判断是否配置了扫包
		if (springBootApplicationRunClass != null) {
			Annotation saScanAnnotation = springBootApplicationRunClass.getAnnotation(SaScan.class);
			if(saScanAnnotation != null) {
				SaScan saScan = (SaScan) saScanAnnotation;
				String packageName = saScan.path();
				String url = saScan.url();
				// 这里一般我们是通过反射获取需要代理的接口的clazz列表
				// 比如判断包下面的类，或者通过某注解标注的类等等
				List<Class<?>> beanClazzs = ClassUtil.getClasses(packageName);
				Set<Class<?>> beanClazzsInterface = new LinkedHashSet<>();
				for (Class beanClazz : beanClazzs) {
					Annotation annotation = beanClazz.getAnnotation(SaFeignClient.class);
					if (annotation != null) {
						beanClazzsInterface.add(beanClazz);
					}
				}
				for (Class beanClazz : beanClazzsInterface) {
					BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
					GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();

					// 在这里，我们可以给该对象的属性注入对应的实例。
					// 比如mybatis，就在这里注入了dataSource和sqlSessionFactory，
					// 注意，如果采用definition.getPropertyValues()方式的话，
					// 类似definition.getPropertyValues().add("interfaceType", beanClazz);
					// 则要求在FactoryBean（本应用中即ServiceFactory）提供setter方法，否则会注入失败
					// 如果采用definition.getConstructorArgumentValues()，
					// 则FactoryBean中需要提供包含该属性的构造方法，否则会注入失败
					definition.getConstructorArgumentValues().addIndexedArgumentValue(0, beanClazz);
					definition.getConstructorArgumentValues().addIndexedArgumentValue(1, url);

					// 注意，这里的BeanClass是生成Bean实例的工厂，不是Bean本身。
					// FactoryBean是一种特殊的Bean，其返回的对象不是指定类的一个实例，
					// 其返回的是该工厂Bean的getObject方法所返回的对象。
					definition.setBeanClass(ServiceFactory.class);

					// 这里采用的是byType方式注入，类似的还有byName等
					definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
					registry.registerBeanDefinition(beanClazz.getSimpleName(), definition);
				}
			}
		}
	}

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

	}

	/**
	 * 初始化配置参数
	 * @return
	 */
	private Class init() {
		log.info("service bean definition registry init");
		Class springBootApplicationRunClass = null;
		try {
			String springBootApplicationRunName = environment.getProperty("skyable.feign.client.class");
			log.info("springBootApplicationRunName:" + springBootApplicationRunName);
			springBootApplicationRunClass = Class.forName(springBootApplicationRunName);
		} catch (Exception ex) {
			log.error("read config file error:", ex);
		}
		return springBootApplicationRunClass;
	}

	@Override
	public void setEnvironment(final Environment environment) {
		this.environment = environment;
	}
}
